package me.zhengjie.modules.web.service.course.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import me.zhengjie.config.entity.PageData;
import me.zhengjie.config.exception.WeChatException;
import me.zhengjie.constant.ConstantUtils;
import me.zhengjie.enums.RedisKeyEnum;
import me.zhengjie.modules.system.domain.*;
import me.zhengjie.modules.system.service.*;
import me.zhengjie.modules.system.service.dto.*;
import me.zhengjie.modules.system.service.mapstruct.CourseClassExpMapper;
import me.zhengjie.modules.system.service.mapstruct.CourseInfoExpMapper;
import me.zhengjie.modules.web.domain.course.*;
import me.zhengjie.modules.web.enums.ApplicantsSignStatusEnum;
import me.zhengjie.modules.web.enums.CockInStatusEnum;
import me.zhengjie.modules.web.enums.VerifyCodeSceneEnum;
import me.zhengjie.modules.web.service.course.ICourseClockWebService;
import me.zhengjie.modules.web.service.course.ICourseService;
import me.zhengjie.modules.web.service.mybatisplus.*;
import me.zhengjie.request.ATMRContext;
import me.zhengjie.utils.DateUtil;
import me.zhengjie.utils.RedisUtils;
import me.zhengjie.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static me.zhengjie.config.enums.AtmrErrorEnum.Class_NotFound_00001;

@Service
public class CourseServiceImpl implements ICourseService {
    private static final Logger log = LoggerFactory.getLogger(CourseServiceImpl.class);
    @Autowired
    private CourseTypeService courseTypeService;

    @Autowired
    private CourseInfoService courseInfoService;

    @Autowired
    private ICourseInfoPlusService courseInfoPlusService;

    @Autowired
    private WxUserService wxUserService;

    @Autowired
    private IWxUserPlusService wxUserPlusService;

    @Autowired
    private CourseClockService courseClockService;

    @Autowired
    private ICourseClockPlusService courseClockPlusService;

    @Autowired
    private CourseClassService courseClassService;

    @Autowired
    private CourseClassStudentService courseClassStudentService;

    @Autowired
    private CourseSigninService courseSigninService;

    @Autowired
    private ICourseSigninPlusService  courseSigninPlusService;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private CourseItemService courseItemService;

    @Autowired
    private CourseOrderService courseOrderService;

    @Autowired
    private CourseClockConfigService courseClockConfigService;

    @Autowired
    private IdentityItemService identityItemService;

    @Autowired
    private ICourseClassStudentPlusService courseClassStudentPlusService;

    @Autowired
    private ICourseSigninConfigPlusService signinConfigPlusService;

    @Autowired
    private ICourseClockWebService courseClockWebService;

    @Autowired
    private ICourseApplicantsPlusService applicantsPlusService;
    @Autowired
    private CourseInfoExpMapper courseInfoExpMapper;

    @Autowired
    private CourseClassExpMapper courseClassExpMapper;

    @Autowired
    private PosterService  posterService;
    @Override
    public List<CourseIndexResp> courseIndex() {

        CourseTypeQueryCriteria criteria = new CourseTypeQueryCriteria();
        criteria.setTypeStatus(ConstantUtils.Valid);
        criteria.setShowIndex(ConstantUtils.Valid);
        List<CourseTypeDto> typeList = courseTypeService.queryAll(criteria);
        if (CollectionUtils.isEmpty(typeList)) {
            return null;
        }
        List<CourseIndexResp> list = new ArrayList<>();
        for (CourseTypeDto courseTypeDto : typeList) {
            CourseIndexResp resp = new CourseIndexResp();
            resp.setTypeId(courseTypeDto.getTypeId());
            resp.setTypeName(courseTypeDto.getTypeName());
            resp.setTypeIcon(courseTypeDto.getTypeIcon());

            CourseInfoQueryCriteria infoQueryCriteria = new CourseInfoQueryCriteria();
            infoQueryCriteria.setTypeId(courseTypeDto.getTypeId());
            infoQueryCriteria.setShowIndex(ConstantUtils.Valid);
            infoQueryCriteria.setStatus(ConstantUtils.Valid);
            PageRequest page = PageRequest.of(0, 4, Sort.by(Sort.Order.asc("sortIndex")));
            Map<String, Object> infos = courseInfoService.queryAll(infoQueryCriteria, page);
            if (MapUtil.isEmpty(infos)) {
                list.add(resp);
                continue;
            }
            List<CourseInfoDto> courseInfoDtoList = (List<CourseInfoDto>) infos.get("content");
            List<CourseInfoResp> courseList = new ArrayList<>();
            for (CourseInfoDto info : courseInfoDtoList) {
                CourseInfoResp infoResp = new CourseInfoResp();
                infoResp.setCourseId(info.getCourseId());
                infoResp.setCourseNo(info.getCourseNo());
                infoResp.setCourseCover(info.getCourseLogo());
                infoResp.setCourseName(info.getCourseName());
                infoResp.setPrice(info.getSellPrice());
                infoResp.setSubTitle(info.getSubTitle());
                Integer showDiscount = info.getShowDiscount();
                if (Integer.valueOf(ConstantUtils.Valid).equals(showDiscount)){
                    infoResp.setDiscountPrice(info.getDiscountPrice());
                }else {
                    infoResp.setDiscountPrice(BigDecimal.ZERO);
                }
                infoResp.setCourseId(info.getCourseId());
                infoResp.setOpenTime(DateUtil.getTimeStamp(info.getOpenTime()));
                infoResp.setCategory(info.getCategory());
                CourseClass classInfo = courseClassService.getClassByCourseNo(info.getCourseNo());
                if (Objects.nonNull(classInfo)) {
                    infoResp.setClassName(classInfo.getName());
                }
                infoResp.setShowPrice(Objects.isNull(info.getShowPrice()) || info.getShowPrice() == 1);
                courseList.add(infoResp);
            }
            resp.setCourseList(courseList);
            list.add(resp);
        }
        return list;
    }

    @Override
    public List<CourseIndexResp> courseList(CourseListReq req) {
        CourseTypeQueryCriteria criteria = new CourseTypeQueryCriteria();
        criteria.setTypeStatus(ConstantUtils.Valid);
        List<CourseTypeDto> typeList = courseTypeService.queryAll(criteria);
        if (CollectionUtils.isEmpty(typeList)) {
            return null;
        }
        List<CourseIndexResp> list = new ArrayList<>();
        for (CourseTypeDto courseTypeDto : typeList) {
            CourseIndexResp resp = new CourseIndexResp();
            resp.setTypeId(courseTypeDto.getTypeId());
            resp.setTypeName(courseTypeDto.getTypeName());
            resp.setTypeIcon(courseTypeDto.getTypeIcon());

            /*CourseInfoQueryCriteria infoQueryCriteria = new CourseInfoQueryCriteria();
            infoQueryCriteria.setTypeId(courseTypeDto.getTypeId());
            infoQueryCriteria.setStatus(ConstantUtils.Valid);
            List<CourseInfoDto> infos = courseInfoService.queryAll(infoQueryCriteria);*/
            LambdaQueryWrapper<CourseInfo> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(CourseInfo::getTypeId,courseTypeDto.getTypeId());
            wrapper.eq(CourseInfo::getStatus,ConstantUtils.Valid);
            wrapper.orderByAsc(CourseInfo::getSortIndex).orderByDesc(CourseInfo::getUpdateTime);
            List<CourseInfo> infos = courseInfoPlusService.list(wrapper);
            if (CollectionUtils.isEmpty(infos)) {
                list.add(resp);
                continue;
            }
            List<CourseInfoResp> courseList = new ArrayList<>();
            for (CourseInfo info : infos) {
                CourseInfoResp infoResp = new CourseInfoResp();
                infoResp.setCourseId(info.getCourseId());
                infoResp.setCourseNo(info.getCourseNo());
                infoResp.setCourseCover(info.getCourseLogo());
                infoResp.setCourseName(info.getCourseName());
                infoResp.setPrice(info.getSellPrice());
                Integer showDiscount = info.getShowDiscount();
                if (Integer.valueOf(ConstantUtils.Valid).equals(showDiscount)){
                    infoResp.setDiscountPrice(info.getDiscountPrice());
                }else {
                    infoResp.setDiscountPrice(BigDecimal.ZERO);
                }
                infoResp.setCourseId(info.getCourseId());
                infoResp.setOpenTime(DateUtil.getTimeStamp(info.getOpenTime()));
                infoResp.setClassName(info.getSubTitle());
                infoResp.setSubTitle(info.getSubTitle());
                infoResp.setCategory(info.getCategory());
                CourseClass classInfo = courseClassService.getClassByCourseNo(info.getCourseNo());
                if (Objects.nonNull(classInfo)) {
                    infoResp.setClassName(classInfo.getName());
                }
                infoResp.setShowPrice(Objects.isNull(info.getShowPrice()) || info.getShowPrice() == 1);
                courseList.add(infoResp);
            }
            resp.setCourseList(courseList);
            list.add(resp);
        }
        return list;
    }


    @Override
    public CourseDetailResp courseDetail(String courseNo) {
        if (StringUtils.isBlank(courseNo)) {
            throw new WeChatException("课程编号不能为空");
        }
        String openId = ATMRContext.getContext().getOpenId();
        courseNo = getRealCourseNo(courseNo, openId);
        CourseInfo info = getCourseInfoByCache(courseNo);
        if (Objects.isNull(info)) {
            throw new WeChatException("课程不存在");
        }
        CourseDetailResp infoResp = new CourseDetailResp();
        infoResp.setShowPrice(Objects.isNull(info.getShowPrice()) || info.getShowPrice() == 1);
        infoResp.setShowApplicants(Objects.isNull(info.getOpenBuy()) || info.getOpenBuy() == 1);
        infoResp.setCourseId(info.getCourseId());
        infoResp.setCourseNo(info.getCourseNo());
        infoResp.setCourseCover(info.getCourseBanner());
        infoResp.setName(info.getCourseName());
        infoResp.setPrice(info.getSellPrice());
        infoResp.setPlatform(info.getPlatform());
        infoResp.setNeedApplicants(ConstantUtils.Applicants.equals(info.getApplicants()));
        Integer showDiscount = info.getShowDiscount();
        if (Integer.valueOf(ConstantUtils.Valid).equals(showDiscount)){
            infoResp.setDiscountPrice(info.getDiscountPrice());
        }else {
            infoResp.setDiscountPrice(BigDecimal.ZERO);
        }
        infoResp.setCourseId(info.getCourseId());
        infoResp.setOpenTime(DateUtil.getTimeStamp(info.getOpenTime()));
        String courseInfo = info.getCourseInfo();
        infoResp.setCourseText(replaceImageSrc(courseInfo));
        infoResp.setSubTitle(info.getSubTitle());
        infoResp.setCategory(info.getCategory());
        CourseClass classInfo = courseClassService.getClassByCourseNo(info.getCourseNo());
        if (Objects.nonNull(classInfo)) {
            infoResp.setClassName(classInfo.getName());
            infoResp.setClassNo(classInfo.getClassNo());
            infoResp.setMeals(classInfo.getMeals());
        }
        CourseClassStudentQueryCriteria queryCriteria = new CourseClassStudentQueryCriteria();
        queryCriteria.setCourseNo(courseNo);
        queryCriteria.setOpenId(openId);
        List<CourseClassStudentDto> studentDtoList = courseClassStudentService.queryAll(queryCriteria);
        if (CollectionUtils.isNotEmpty(studentDtoList)){
            infoResp.setMealsPaid(ConstantUtils.Meals_Payed.equals(studentDtoList.get(0).getMealsStatus()));
        }

        CourseOrderQueryCriteria orderQueryCriteria = new CourseOrderQueryCriteria();
        orderQueryCriteria.setCourseNo(courseNo);
        orderQueryCriteria.setPayStatus(ConstantUtils.Order_Payed);
        orderQueryCriteria.setOpenId(openId);
        List<CourseOrderDto> courseOrderDtos = courseOrderService.queryAll(orderQueryCriteria);
        boolean payed = CollectionUtils.isNotEmpty(courseOrderDtos);
        infoResp.setHasPaid(payed);
        //查询评论
        List<String> courseClockClassNoList = courseClockWebService.getCourseClockClassNoList(courseNo);
        if (Objects.nonNull(classInfo)) {
            courseClockClassNoList.add(classInfo.getClassNo());
        }
        List<CourseClock> clockList = courseClockWebService.queryExcellent(courseClockClassNoList);
        if (CollectionUtils.isNotEmpty(clockList)) {
            List<CourseDetailResp.CourseComment> courseComments = Collections.synchronizedList(new ArrayList<>());
            clockList.parallelStream().forEach(s -> {
                CourseDetailResp.CourseComment courseComment = new CourseDetailResp.CourseComment();
                courseComment.setComment(s.getClockContent());
                courseComment.setOpenId(s.getOpenId());
                courseComment.setCreateTime(DateUtil.formatDate(s.getCreateTime(), DateUtil.DATE_PATTERN_YYYY_MM_DD));
                LambdaQueryWrapper<WxUser> userWrapper = new LambdaQueryWrapper<>();
                userWrapper.eq(WxUser::getOpenId,s.getOpenId());
                WxUser wxuser = wxUserPlusService.getOne(userWrapper);
                if (Objects.nonNull(wxuser)) {
                    courseComment.setName(wxuser.getRealName());
                    courseComment.setHeadPic(wxuser.getHeadimgurl());
                    IdentityItem one = identityItemService.findOne(wxuser.getIdentityNo());
                    if (Objects.nonNull(one)){
                        courseComment.setIdentity(one.getTitle());
                    }else {
                        courseComment.setIdentity(ConstantUtils.Simple_Member);
                    }
                }
                courseComments.add(courseComment);
            });
            infoResp.setCommentList(courseComments.parallelStream().sorted(Comparator.comparing(CourseDetailResp.CourseComment::getCreateTime,Comparator.reverseOrder())).collect(Collectors.toList()));
        }

        CourseItemQueryCriteria itemQueryCriteria = new CourseItemQueryCriteria();
        itemQueryCriteria.setCourseNo(courseNo);
        List<CourseItemDto> itemDtoList = courseItemService.queryAll(itemQueryCriteria);
        if (CollectionUtils.isNotEmpty(itemDtoList)) {
            List<CourseDetailResp.CourseCatalog> categoryList = Collections.synchronizedList(new ArrayList<>());
            itemDtoList.parallelStream().forEach(s -> {
                CourseDetailResp.CourseCatalog catalog = new CourseDetailResp.CourseCatalog();
                catalog.setDuration(s.getDuration());
                catalog.setViewStatus(s.getTrysee());
                if (payed){
                    catalog.setViewStatus(ConstantUtils.Valid);
                }
                catalog.setTitle(s.getItemTitle());
                catalog.setUrl(s.getItemValue());
                catalog.setItemId(s.getItemId());
                categoryList.add(catalog);
            });
            List<CourseDetailResp.CourseCatalog> collect = categoryList.parallelStream().sorted(Comparator.comparing(CourseDetailResp.CourseCatalog::getItemId)).collect(Collectors.toList());
            infoResp.setCatalogList(collect);
        }

        LambdaQueryWrapper<CourseApplicants> applicantsWrapper = new LambdaQueryWrapper<>();
        applicantsWrapper.eq(CourseApplicants::getCourseNo,courseNo);
        //applicantsWrapper.eq(CourseApplicants::getOpenId,openId);
        List<CourseApplicants> list = applicantsPlusService.list(applicantsWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return infoResp;
        }
        WxUser wxUser = wxUserService.findByOpenId(openId);
        list = list.stream().filter(s -> s.getOpenId().equals(openId) || s.getMobile().equals(wxUser.getMobile())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(list)) {
            return infoResp;
        }
        List<CourseDetailResp.CourseApplicantsResp> courseApplicantsResps = new ArrayList<>();
        list.forEach(s -> {
            CourseDetailResp.CourseApplicantsResp applicantsResp = new CourseDetailResp.CourseApplicantsResp();
            applicantsResp.setMobile(s.getMobile());
            applicantsResp.setRealName(s.getRealName());
            applicantsResp.setSelf(s.getOneself().equals(ConstantUtils.Applicants));
            applicantsResp.setPaid(s.getPaid().equals(ConstantUtils.Order_Payed));
            applicantsResp.setConsume(s.getConsume().equals(ConstantUtils.Order_Payed));
            applicantsResp.setId(String.valueOf(s.getId()));
            courseApplicantsResps.add(applicantsResp);
        });
        infoResp.setApplicantsList(courseApplicantsResps);
        return infoResp;
    }

    @Override
    public boolean clickIn(CourseClockInReq req) {
        String openId = ATMRContext.getContext().getOpenId();
        if (StringUtils.isAnyBlank(req.getClockText(), req.getCourseNo(), openId)) {
            throw new WeChatException("缺少参数");
        }
        WxUser wxUser = wxUserService.findByOpenId(openId);
        if (Objects.isNull(wxUser)) {
            throw new WeChatException("openId传递有误");
        }

        CourseInfo courseInfo = getCourseInfoByCache(req.getCourseNo());
        if (Objects.isNull(courseInfo)) {
            throw new WeChatException("课程编号传递有误");
        }

        CourseClass classInfo = courseClassService.getClassByCourseNo(req.getCourseNo());
        if (Objects.isNull(classInfo)) {
            throw new WeChatException("未找到班级");
        }
        CourseClockConfigQueryCriteria clockConfigQueryCriteria = new CourseClockConfigQueryCriteria();
        clockConfigQueryCriteria.setCourseNo(req.getCourseNo());
        clockConfigQueryCriteria.setStatus(ConstantUtils.Valid);
        List<CourseClockConfigDto> courseClockConfigDtos = courseClockConfigService.queryAll(clockConfigQueryCriteria);
        if (CollectionUtils.isEmpty(courseClockConfigDtos)){
            throw new WeChatException("打开配置未配");
        }
        LambdaQueryWrapper<CourseClassStudent> studentWrapper = new LambdaQueryWrapper<>();
        studentWrapper.eq(CourseClassStudent::getOpenId,openId);
        studentWrapper.eq(CourseClassStudent::getCourseNo,req.getCourseNo());
        List<CourseClassStudent> studentList = courseClassStudentPlusService.list(studentWrapper);
        if (CollectionUtils.isEmpty(studentList)){
            throw new WeChatException("非本班学员不可打卡哦");
        }
        CourseClassStudent student = studentList.get(0);
        String clockDate = req.getClockDate();
        Timestamp clockTimestamp;
        if (StringUtils.isNotBlank(clockDate)){
            clockTimestamp = new Timestamp(DateUtil.string2Date(req.getClockDate(),"yyyy-MM-dd").getTime());
        }else {
            clockTimestamp = new Timestamp(DateUtil.string2Date(DateUtil.getNowDate()).getTime());
        }
        /*LambdaQueryWrapper<CourseClock> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseClock::getCourseNo,req.getCourseNo());
        wrapper.eq(CourseClock::getOpenId,openId);
        wrapper.eq(CourseClock::getCreateTime,clockTimestamp);
        List<CourseClock> list = courseClockPlusService.list(wrapper);
        if (CollectionUtils.isNotEmpty(list)){
            throw new WeChatException("不要重复打卡哦");
        }*/

        CourseClockConfigDto courseClockConfigDto = courseClockConfigDtos.get(0);
        CourseClock courseClock = new CourseClock();
        courseClock.setOpenId(openId);
        courseClock.setNickName(wxUser.getNickname());
        courseClock.setMobile(wxUser.getMobile());
        courseClock.setCourseNo(req.getCourseNo());
        courseClock.setUserName(wxUser.getRealName());
        courseClock.setClockContent(req.getClockText());

        courseClock.setCreateTime(clockTimestamp);
        courseClock.setClockClass(classInfo.getName());
        courseClock.setClassNo(classInfo.getClassNo());
        courseClock.setVisibleAuth(courseClockConfigDto.getVisibleAuth());
        courseClock.setClockNo(UUID.fastUUID().toString(true));
        courseClock.setGroupNumber(student.getGroupNumber());
        courseClock.setUpdateTime(DateUtil.TimestampNow());
        if (courseClockConfigDto.getRequiredAudit() == ConstantUtils.Valid){
            courseClock.setStatus(CockInStatusEnum.ToBeReview.getStatus());
        }else {
            courseClock.setStatus(CockInStatusEnum.ReviewPass.getStatus());
        }
        courseClockService.create(courseClock);
        return true;
    }

    @Override
    public CourseSignInDetailResp getSignInDetail(CourseSignInDetailReq req) {

        String openId = ATMRContext.getContext().getOpenId();
        if (StringUtils.isAnyBlank(openId, req.getCourseNo())) {
            throw new WeChatException("缺少参数");
        }
        CourseSignInDetailResp signInResp = new CourseSignInDetailResp();

        String courseNo = req.getCourseNo();
        try {
            courseNo = getRealCourseNo(courseNo, openId);
        } catch (WeChatException e) {
            signInResp.setNoCourse(1);
            signInResp.setNoCourseTip(e.getMsg());
            List<String> posterList = getPosterList();
            signInResp.setImgUrlList(posterList);
            if (CollectionUtils.isNotEmpty(posterList)){
                signInResp.setImgUrl(posterList.get(0));
            }
            return signInResp;
        }

        CourseClass classByCourseNo = courseClassService.getClassByCourseNo(courseNo);
        if (Objects.isNull(classByCourseNo)) {
            throw new WeChatException("课程不存在");
        }
        CourseOrderQueryCriteria orderQueryCriteria = new CourseOrderQueryCriteria();
        orderQueryCriteria.setCourseNo(classByCourseNo.getCourseNo());
        orderQueryCriteria.setPayStatus(ConstantUtils.Order_Payed);
        List<CourseOrderDto> courseOrderDtos = courseOrderService.queryAll(orderQueryCriteria);
        if (CollectionUtils.isNotEmpty(courseOrderDtos)){
            signInResp.setHasBuy(true);
        }
        signInResp.setSeatingMapImage(classByCourseNo.getSeatingMapImage());
        signInResp.setCourseNo(classByCourseNo.getCourseNo());
        signInResp.setMeals(classByCourseNo.getMeals());
        CourseSigninQueryCriteria signinQueryCriteria = new CourseSigninQueryCriteria();
        signinQueryCriteria.setOpenId(openId);
        signinQueryCriteria.setCourseNo(req.getCourseNo());
        List<CourseSigninDto> courseSigninDtos = courseSigninService.queryAll(signinQueryCriteria);
        if (CollectionUtils.isNotEmpty(courseSigninDtos)) {
            CourseSigninDto courseSigninDto = courseSigninDtos.get(0);
            //查询配置
            LambdaQueryWrapper<CourseSigninConfig> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(CourseSigninConfig::getCourseNo,req.getCourseNo());
            List<CourseSigninConfig> list = signinConfigPlusService.list(wrapper);
            if (CollectionUtils.isNotEmpty(list)){
                Date dayStartDate = DateUtil.getDayStartDate(new Date());
                Date dayEndDate = DateUtil.getDayEndDate(new Date());
                Timestamp start = DateUtil.dateConvertTimestamp(dayStartDate);
                Timestamp end = DateUtil.dateConvertTimestamp(dayEndDate);
                CourseSigninDto checkRange = courseSigninDtos.parallelStream().filter(s -> s.getCreateTime().after(start) && s.getCreateTime().before(end)).findFirst().orElse(null);
                if (Objects.nonNull(checkRange)){
                    signInResp.setHasSignIn(true);
                    signInResp.setGroupNumber(checkRange.getGroupNo());
                }
            }else {
                signInResp.setHasSignIn(true);
                signInResp.setGroupNumber(courseSigninDto.getGroupNo());
            }
        }
        CourseClassStudentQueryCriteria queryCriteria = new CourseClassStudentQueryCriteria();
        queryCriteria.setClassNo(classByCourseNo.getClassNo());
        queryCriteria.setOpenId(openId);
        List<CourseClassStudentDto> studentDtoList = courseClassStudentService.queryAll(queryCriteria);
        if (CollectionUtils.isNotEmpty(studentDtoList)){
            signInResp.setPayMeals(ConstantUtils.Meals_Payed.equals(studentDtoList.get(0).getMealsStatus()));
        }
        if (signInResp.isHasSignIn()){
            signInResp.setPayMeals(true);
        }

        LambdaQueryWrapper<CourseSignin> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseSignin::getOpenId,openId);
        wrapper.orderByDesc(CourseSignin::getCreateTime);
        List<CourseSignin> list = courseSigninPlusService.list(wrapper);
        if (CollectionUtils.isNotEmpty(list)){
            signInResp.setLastSignUserName(list.get(0).getName());
            signInResp.setLastSignUserMobile(list.get(0).getMobile());
        }
        log.info("======================signInResp="+ JSON.toJSONString(signInResp));
        return signInResp;
    }

    private String getRealCourseNo(String courseNo, String openId) {
        if (courseNo.equals(ConstantUtils.SIGN_IN_VERSION)){
            LambdaQueryWrapper<WxUser> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(WxUser::getOpenId, openId);
            List<WxUser> list = wxUserPlusService.list(wrapper);
            if (CollectionUtils.isEmpty(list) || StringUtils.isBlank(list.get(0).getMobile())){
                //throw new WeChatException("请去个人中心先完善个人资料");
                return newUserGetSignCourseNo();
            }
            LambdaQueryWrapper<CourseClassStudent> studentWrapper = new LambdaQueryWrapper<>();
            studentWrapper.eq(CourseClassStudent::getPhoneNo,list.get(0).getMobile());
            studentWrapper.orderByDesc(CourseClassStudent::getCreateTime);
            List<CourseClassStudent> studentList = courseClassStudentPlusService.list(studentWrapper);
            if (CollectionUtils.isEmpty(studentList)){
                //throw new WeChatException("未加入到班级无法签到");
                return newUserGetSignCourseNo();
            }
            List<String> distinctList = studentList.stream().sorted(Comparator.comparing(CourseClassStudent::getCreateTime)).map(CourseClassStudent::getCourseNo).distinct().collect(Collectors.toList());

            String respCourseNo = "";
            //根据课程编号查询签到配置
            for (String courseNos : distinctList) {
                LambdaQueryWrapper<CourseSigninConfig> signinConfigWrapper = new LambdaQueryWrapper<>();
                signinConfigWrapper.eq(CourseSigninConfig::getCourseNo,courseNos);
                List<CourseSigninConfig> signinConfigList = signinConfigPlusService.list(signinConfigWrapper);
                if (CollectionUtils.isEmpty(signinConfigList)){
                    continue;
                }
                Timestamp timestamp = DateUtil.TimestampNow();
                boolean checkRange = signinConfigList.stream().anyMatch(s -> DateUtil.stringToTimeStamp(s.getStartTime()).before(timestamp) && DateUtil.stringToTimeStamp(s.getEndTime()).after(timestamp));
                if (!checkRange){
                    continue;
                }
                respCourseNo = courseNos;
                break;
            }
            if (StringUtils.isBlank(respCourseNo)){
                //throw new WeChatException(Class_NotFound_00001.getErrorCode(),Class_NotFound_00001.getErrorMessage());
                return newUserGetSignCourseNo();
            }
            courseNo = respCourseNo;
            //courseNo = studentList.get(0).getCourseNo();
        }
        return courseNo;
    }

    private String newUserGetSignCourseNo(){
        LambdaQueryWrapper<CourseSigninConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.lt(CourseSigninConfig::getStartTime, new Date()).gt(CourseSigninConfig::getEndTime, new Date());
        wrapper.orderByDesc(CourseSigninConfig::getCreateTime);
        List<CourseSigninConfig> list = signinConfigPlusService.list(wrapper);
        if (CollectionUtils.isEmpty(list)){
            throw new WeChatException("没有可签到班级");
        }
        return list.get(0).getCourseNo();
    }
    @Override
    public CourseSignInResp signin(CourseSignInReq req) {
        String openId = ATMRContext.getContext().getOpenId();
        if (StringUtils.isAnyBlank(openId, req.getCourseNo(), req.getMobile(), req.getName(), req.getCode())) {
            throw new WeChatException("缺少参数");
        }
        String key = RedisKeyEnum.SignIn.getKey() + openId;
        boolean result = redisUtils.setIfAbsent(key, openId, RedisKeyEnum.SignIn.getExpire(), TimeUnit.SECONDS);
        if (!result) {
            throw new WeChatException("sign_0001","签到中，请勿重复点击签到");
        }
        Object value = redisUtils.get(VerifyCodeSceneEnum.getRedisKey(req.getMobile(), VerifyCodeSceneEnum.SignIn));
        if (Objects.isNull(value) || !String.valueOf(value).equals(req.getCode())) {
            throw new WeChatException("验证码不正确");
        }
        LambdaQueryWrapper<CourseSigninConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseSigninConfig::getCourseNo,req.getCourseNo());
        List<CourseSigninConfig> list = signinConfigPlusService.list(wrapper);
        if (CollectionUtils.isEmpty(list)){
            throw new WeChatException("班级未配置签到时段");
        }
        Timestamp timestamp = DateUtil.TimestampNow();
        boolean checkRange = list.stream().anyMatch(s -> DateUtil.stringToTimeStamp(s.getStartTime()).before(timestamp) && DateUtil.stringToTimeStamp(s.getEndTime()).after(timestamp));
        if (!checkRange){
            throw new WeChatException("不在签到时间范围内");
        }
        CourseSigninQueryCriteria signinQueryCriteria = new CourseSigninQueryCriteria();
        signinQueryCriteria.setOpenId(openId);
        signinQueryCriteria.setCourseNo(req.getCourseNo());
        signinQueryCriteria.setCreateTime(Lists.newArrayList(DateUtil.dateConvertTimestamp(DateUtil.getDayStartDate(new Date())),DateUtil.dateConvertTimestamp(DateUtil.getDayEndDate(new Date()))));
        List<CourseSigninDto> courseSigninDtos = courseSigninService.queryAll(signinQueryCriteria);
        if (CollectionUtils.isNotEmpty(courseSigninDtos)) {
            boolean check = list.parallelStream().anyMatch(s -> courseSigninDtos.stream().anyMatch(t -> t.getCreateTime().after(DateUtil.stringToTimeStamp(s.getStartTime())) && t.getCreateTime().before(DateUtil.stringToTimeStamp(s.getEndTime()))));
            if (check){
                throw new WeChatException("已签到");
            }
            //throw new WeChatException("已签到");
        }

        LambdaQueryWrapper<CourseClassStudent> studentQueryCriteria = new LambdaQueryWrapper<>();
        studentQueryCriteria.eq(CourseClassStudent::getCourseNo,req.getCourseNo());
        List<CourseClassStudent> studentDtoList = courseClassStudentPlusService.list(studentQueryCriteria);
        if (CollectionUtils.isEmpty(studentDtoList)) {
            throw new WeChatException("信息未维护，请联系工作人员");
        }
        studentDtoList = studentDtoList.stream().filter(s -> StringUtils.isNotBlank(s.getUserName()) && StringUtils.isNotBlank(s.getPhoneNo())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(studentDtoList)) {
            throw new WeChatException("信息未维护，请联系工作人员");
        }
        boolean matchName = studentDtoList.parallelStream().anyMatch(s -> s.getUserName().equals(req.getName()));
        if (!matchName) {
            throw new WeChatException("用户名输入有误");
        }
        boolean matchMobile = studentDtoList.parallelStream().anyMatch(s -> s.getPhoneNo().equals(req.getMobile()));
        if (!matchMobile) {
            throw new WeChatException("电话输入有误");
        }
        CourseClassStudent studentDto = studentDtoList.parallelStream().filter(s -> s.getUserName().equals(req.getName()) && s.getPhoneNo().equals(req.getMobile())).findFirst().orElse(null);
        if (Objects.isNull(studentDto)){
            throw new WeChatException("姓名/电话都匹配的记录不存在");
        }
        if (StringUtils.isBlank(studentDto.getOpenId())){
            CourseClassStudent student = new CourseClassStudent();
            student.setOpenId(openId);
            student.setUpdateTime(DateUtil.TimestampNow());
            LambdaQueryWrapper<CourseClassStudent> modify = new LambdaQueryWrapper<>();
            modify.eq(CourseClassStudent::getStudentId,studentDto.getStudentId());
            courseClassStudentPlusService.update(student,modify);
        }
        CourseClass classInfo = courseClassService.getClassByCourseNo(req.getCourseNo());
        CourseInfo courseInfo = getCourseInfoByCache(req.getCourseNo());

        CourseSignin courseSignin = new CourseSignin();
        courseSignin.setCourseNo(req.getCourseNo());
        courseSignin.setMobile(req.getMobile());
        courseSignin.setName(req.getName());
        courseSignin.setCreateTime(DateUtil.TimestampNow());
        courseSignin.setOpenId(openId);
        courseSignin.setPayMeals(studentDto.getMealsStatus());
        courseSignin.setCourseName(courseInfo.getCourseName());
        courseSignin.setClassNo(classInfo.getClassNo());
        courseSignin.setClassName(classInfo.getName());
        courseSignin.setGroupNo(studentDto.getGroupNumber());
        courseSignin.setChannelSource(studentDto.getChannelSource());
        courseSigninService.create(courseSignin);

        //异步更新用户表
        ThreadUtil.execAsync(() -> {
            WxUser wxUser = wxUserService.findByOpenId(openId);
            if (Objects.isNull(wxUser)){
                wxUser = new WxUser();
            }
            wxUser.setOpenId(openId);
            wxUser.setRealName(req.getName());
            wxUser.setMobile(req.getMobile());
            wxUser.setUpdateTime(DateUtil.TimestampNow());
            try {
                wxUserPlusService.saveOrUpdate(wxUser);
            } catch (Exception e) {
                e.printStackTrace();
            }
           /* LambdaQueryWrapper<CourseApplicants> applicantsLambdaQueryWrapper = new LambdaQueryWrapper<>();
            applicantsLambdaQueryWrapper.eq(CourseApplicants::getCourseNo,req.getCourseNo());
            applicantsLambdaQueryWrapper.eq(CourseApplicants::getMobile,req.getMobile());
            List<CourseApplicants> applicantsList = applicantsPlusService.list(applicantsLambdaQueryWrapper);
            Integer signStatus = ApplicantsSignStatusEnum.OnLine.getSignStatus();
            if (CollectionUtils.isNotEmpty(applicantsList)){
                CourseApplicants applicants = applicantsList.get(0);
                if (ApplicantsModifyFlagEnum.StudentModifyMobile.getFlag().equals(applicants.getModifyFlag())){
                    signStatus = ApplicantsSignStatusEnum.OffLine.getSignStatus();
                }
            }*/
            //更新报名核销
            CourseApplicants applicants = new CourseApplicants();
            applicants.setConsume(ConstantUtils.Valid);
            applicants.setSignStatus(ApplicantsSignStatusEnum.OnLine.getSignStatus());
            LambdaQueryWrapper<CourseApplicants> applicantsWrapper = new LambdaQueryWrapper<>();
            applicantsWrapper.eq(CourseApplicants::getCourseNo,req.getCourseNo());
            applicantsWrapper.eq(CourseApplicants::getMobile,req.getMobile());
            applicantsPlusService.update(applicants,applicantsWrapper);
        });
        CourseSignInResp resp = new CourseSignInResp();
        resp.setSignin(true);
        resp.setCourseNo(req.getCourseNo());
        resp.setPayMeals(ConstantUtils.Meals_Payed.equals(studentDto.getMealsStatus()));
        resp.setMeals(classInfo.getMeals());
        resp.setUserName(req.getName());
        resp.setSeatingMapImage(classInfo.getSeatingMapImage());
        resp.setGroupNumber(studentDto.getGroupNumber());
        return resp;
    }

    @Override
    public List<CourseClockInListResp> clockInListV2(CourseClockInListRequest request) {
        LambdaQueryWrapper<CourseInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(CourseInfo::getCreateTime);
        Page<CourseInfo> page = Page.of(1, 10);
        Page<CourseInfo> courseInfoPage = courseInfoExpMapper.selectPage(page,wrapper);
        if (Objects.isNull(courseInfoPage) || CollectionUtils.isEmpty(courseInfoPage.getRecords())){
            return null;
        }
        List<CourseInfo> list = courseInfoPage.getRecords();
        if (CollectionUtils.isEmpty(list)){
            return null;
        }
        String openId = ATMRContext.getContext().getOpenId();
        List<CourseClockInListResp> resps = new ArrayList<>();
        try {
            for (CourseInfo courseInfo : list) {
                String courseNo = courseInfo.getCourseNo();
                CourseClockInListResp resp = new CourseClockInListResp();
                CourseInfo infoByNo = getCourseInfoByCache(courseNo);
                if (Objects.isNull(infoByNo)){
                    continue;
                }
                CourseClass courseClass = courseClassService.getClassByCourseNo(courseNo);
                if (Objects.isNull(courseClass)){
                    continue;
                }
                resp.setHasAccessRight(true);
                LambdaQueryWrapper<CourseClassStudent> studentWrapper = new LambdaQueryWrapper<>();
                studentWrapper.eq(CourseClassStudent::getOpenId, openId);
                studentWrapper.eq(CourseClassStudent::getCourseNo,courseInfo.getCourseNo());
                List<CourseClassStudent> studentList = courseClassStudentPlusService.list(studentWrapper);
                if (CollectionUtils.isEmpty(studentList)){
                    resp.setHasAccessRight(false);
                    resp.setNoRightTips("非本班学员不可打卡哦");
                }
                resp.setCourseNo(infoByNo.getCourseNo());
                resp.setCourseName(courseClass.getName());
                resp.setCourseCover(infoByNo.getCourseLogo());
                resp.setOpenTime(infoByNo.getOpenTime());
                LambdaQueryWrapper<CourseClock> wrapper1 = new LambdaQueryWrapper<>() ;
                wrapper1.eq(CourseClock::getCourseNo,courseNo);
                List<CourseClock> courseClockDtos = courseClockPlusService.list(wrapper1);
                if (CollectionUtils.isNotEmpty(courseClockDtos)) {
                    resp.setHomeworkNumber(courseClockDtos.size());
                    long collect = courseClockDtos.parallelStream().filter(distinctByKey(CourseClock::getOpenId)).count();
                    resp.setPeopleNumber((int) collect);
                    long count = courseClockDtos.parallelStream().filter(p -> p.getOpenId().equals(openId)).count();
                    resp.setCompleteDays((int) count);
                }
                resps.add(resp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resps.stream().sorted(Comparator.comparing(CourseClockInListResp::getOpenTime, Comparator.reverseOrder())).collect(Collectors.toList());
    }

    @Override
    public PageData<CourseClockInListResp> clockInListV3(CourseClockInListRequest request) {
        Page<CourseInfo> page = Page.of(request.getPage(), request.getSize());
        LambdaQueryWrapper<CourseInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseInfo::getStatus,ConstantUtils.Valid);
        wrapper.orderByDesc(CourseInfo::getCreateTime);
        Page<CourseInfo> courseInfoPage = courseInfoExpMapper.selectPage(page,wrapper);
        if (Objects.isNull(courseInfoPage) || CollectionUtils.isEmpty(courseInfoPage.getRecords())){
            return null;
        }

        return PageData.valueOf(formatClockPageData(courseInfoPage.getRecords()),courseInfoPage.getTotal(),courseInfoPage.getPages());
    }

    @Override
    public PageData<CourseClockInListResp> clockInListV4(CourseClockInListRequest request) {

        Page<CourseClass> page = Page.of(request.getPage(), request.getSize());
        LambdaQueryWrapper<CourseClass> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseClass::getStatus,ConstantUtils.Valid);
        wrapper.orderByDesc(CourseClass::getCreateTime);
        Page<CourseClass> courseInfoPage = courseClassExpMapper.selectPage(page,wrapper);
        if (Objects.isNull(courseInfoPage) || CollectionUtils.isEmpty(courseInfoPage.getRecords())){
            return null;
        }
        return PageData.valueOf(formatClockPageDataV4(courseInfoPage.getRecords()),courseInfoPage.getTotal(),courseInfoPage.getPages());
    }

    private List<CourseClockInListResp> formatClockPageData(List<CourseInfo> list) {
        String openId = ATMRContext.getContext().getOpenId();
        List<CourseClockInListResp> resps = new ArrayList<>();
        for (CourseInfo courseInfo : list) {
            String courseNo = courseInfo.getCourseNo();
            CourseClockInListResp resp = new CourseClockInListResp();
            CourseClass courseClass = courseClassService.getClassByCourseNo(courseNo);
            String courseName = courseInfo.getCourseName();
            if (Objects.nonNull(courseClass)){
                courseName = courseClass.getName();
            }
            resp.setHasAccessRight(true);
            LambdaQueryWrapper<CourseClassStudent> studentWrapper = new LambdaQueryWrapper<>();
            studentWrapper.eq(CourseClassStudent::getOpenId, openId);
            studentWrapper.eq(CourseClassStudent::getCourseNo,courseInfo.getCourseNo());
            List<CourseClassStudent> studentList = courseClassStudentPlusService.list(studentWrapper);
            if (CollectionUtils.isEmpty(studentList)){
                resp.setHasAccessRight(false);
                resp.setNoRightTips("非本班学员不可打卡哦");
            }
            resp.setCourseNo(courseInfo.getCourseNo());
            //resp.setCourseName(infoByNo.getCourseName());
            resp.setCourseName(courseName);
            resp.setCourseCover(courseInfo.getCourseLogo());
            resp.setOpenTime(courseInfo.getOpenTime());
            LambdaQueryWrapper<CourseClock> wrapper1 = new LambdaQueryWrapper<>() ;
            wrapper1.eq(CourseClock::getCourseNo,courseNo);
            List<CourseClock> courseClockDtos = courseClockPlusService.list(wrapper1);
            if (CollectionUtils.isNotEmpty(courseClockDtos)) {
                resp.setHomeworkNumber(courseClockDtos.size());
                long collect = courseClockDtos.parallelStream().filter(distinctByKey(CourseClock::getOpenId)).count();
                resp.setPeopleNumber((int) collect);
                long count = courseClockDtos.parallelStream().filter(p -> p.getOpenId().equals(openId)).count();
                resp.setCompleteDays((int) count);
            }
            resps.add(resp);
        }
        return resps.stream().sorted(Comparator.comparing(CourseClockInListResp::getOpenTime, Comparator.reverseOrder())).collect(Collectors.toList());
    }

    @Override
    public CourseClockInDetailResp clockInDetail(CourseClockInDetailRequest request) {
        CourseInfo infoByNo = getCourseInfoByCache(request.getCourseNo());
        if (Objects.isNull(infoByNo)){
            throw new WeChatException("非法参数");
        }
        CourseClockConfigQueryCriteria courseClockConfigQueryCriteria = new CourseClockConfigQueryCriteria();
        courseClockConfigQueryCriteria.setCourseNo(request.getCourseNo());
        //courseClockConfigQueryCriteria.setStatus(ConstantUtils.Valid);
        List<CourseClockConfigDto> courseClockConfigDtos = courseClockConfigService.queryAll(courseClockConfigQueryCriteria);
        if (CollectionUtils.isEmpty(courseClockConfigDtos)){
            throw new WeChatException("打卡规则未配置");
        }
        //查询当前用户所在的班级
        LambdaQueryWrapper<CourseClassStudent> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseClassStudent::getOpenId,ATMRContext.getContext().getOpenId());
        List<CourseClassStudent> list = courseClassStudentPlusService.list(wrapper);
        List<String> hasCourseList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(list)){
            hasCourseList = list.parallelStream().map(CourseClassStudent::getCourseNo).collect(Collectors.toList());
        }
        CourseClockConfigDto courseClockConfigDto = courseClockConfigDtos.get(0);
        CourseClockInDetailResp resp = new CourseClockInDetailResp();
        resp.setCourseNo(infoByNo.getCourseNo());
        resp.setCourseName(infoByNo.getCourseName());
        resp.setCourseCover(infoByNo.getCourseLogo());
        resp.setStartClock(DateUtil.getTimeStamp(courseClockConfigDto.getStartDate()));
        resp.setEndClock(DateUtil.getTimeStamp(courseClockConfigDto.getEndDate()));
        resp.setClockRule(courseClockConfigDto.getClockRule());
        resp.setClockDateList(new ArrayList<>());
        CourseClockQueryCriteria clockQueryCriteria = new CourseClockQueryCriteria();
        clockQueryCriteria.setCourseNo(request.getCourseNo());
        clockQueryCriteria.setStatus(ConstantUtils.Valid);
        List<CourseClockDto> courseClockDtos = courseClockService.queryAll(clockQueryCriteria);
        if (CollectionUtils.isNotEmpty(courseClockDtos)) {
            resp.setHomeworkNumber(courseClockDtos.size());
            long collect = courseClockDtos.parallelStream().filter(distinctByKey(CourseClockDto::getOpenId)).count();
            resp.setPeopleNumber((int) collect);
            List<String> userClockList = courseClockDtos.parallelStream().filter(p -> p.getOpenId().equals(request.getOpenId())).map(CourseClockDto::getCreateTime).map(DateUtil::getTimeStamp).collect(Collectors.toList());
            resp.setClockDateList(userClockList);
            CourseClockDto match = courseClockDtos.parallelStream().filter(s -> s.getOpenId().equals(request.getOpenId()) && DateUtil.getDate(DateUtil.getTimeStamp(s.getCreateTime())).equals(LocalDate.now().toString())).findFirst().orElse(null);
            if (Objects.nonNull(match)){
                //resp.setClockInToday(true);
                resp.setClockInContentToday(match.getClockContent());
            }
            long count = courseClockDtos.parallelStream().filter(p -> p.getOpenId().equals(request.getOpenId())).count();
            resp.setCompleteDays((int) count);
            List<CourseClockInDetailResp.Homework> homeworkList = new ArrayList<>();
            List<String> finalHasCourseList = hasCourseList;
            List<CourseClockInDetailResp.Homework> finalHomeworkList = homeworkList;
            courseClockDtos.forEach(s -> {
                //是否全员可见 全员可见：0，班级可见：1
                Integer visibleAuth = s.getVisibleAuth();
                if (visibleAuth == ConstantUtils.Valid && (CollectionUtils.isEmpty(finalHasCourseList) || !finalHasCourseList.contains(s.getCourseNo()))){
                    return;
                }
                CourseClockInDetailResp.Homework homework = new CourseClockInDetailResp.Homework();
                homework.setContent(s.getClockContent());
                WxUser wxUser = wxUserService.findByOpenId(s.getOpenId());
                homework.setRealName(wxUser.getRealName());
                homework.setHeadPic(wxUser.getHeadimgurl());
                Timestamp createTime = s.getCreateTime();
                homework.setClockTime(DateUtil.localDateTimeFormatyMd(createTime.toLocalDateTime()));
                homework.setClockId(s.getClockId());
                finalHomeworkList.add(homework);
            });
            if (CollectionUtils.isNotEmpty(homeworkList)){
                homeworkList = homeworkList.parallelStream().sorted(Comparator.comparing(CourseClockInDetailResp.Homework::getClockId,Comparator.reverseOrder())).collect(Collectors.toList());
            }
            resp.setHomeworkList(homeworkList);
        }
        return resp;
    }


    @Override
    public boolean autoJoinClass(String openId, String courseNo) {
        CourseClass classByCourseNo = courseClassService.getClassByCourseNo(courseNo);
        if (Objects.isNull(classByCourseNo)){
            return false;
        }
        CourseClassStudentQueryCriteria queryCriteria = new CourseClassStudentQueryCriteria();
        queryCriteria.setCourseNo(courseNo);
        queryCriteria.setOpenId(openId);
        List<CourseClassStudentDto> studentDtoList = courseClassStudentService.queryAll(queryCriteria);
        if (CollectionUtils.isNotEmpty(studentDtoList)){
            return false;
        }
        WxUser wxUser = wxUserService.findByOpenId(openId);
        if (Objects.isNull(wxUser)){
            return false;
        }
        CourseClassStudent student = new CourseClassStudent();
        student.setOpenId(openId);
        student.setCourseNo(courseNo);
        student.setClassNo(classByCourseNo.getClassNo());
        student.setPhoneNo(wxUser.getMobile());
        student.setUserName(wxUser.getRealName());
        student.setCreateTime(DateUtil.TimestampNow());
        courseClassStudentService.create(student);
        return true;
    }

    @Override
    public CourseSignInMealsResp getMealsByMobile(CourseSignInMealsReq req) {
        if (StringUtils.isAnyBlank(req.getMobile(),req.getCourseNo())){
            throw new WeChatException("缺少参数");
        }
        LambdaQueryWrapper<CourseClassStudent> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseClassStudent::getCourseNo,req.getCourseNo());
        wrapper.eq(CourseClassStudent::getPhoneNo,req.getMobile());
        List<CourseClassStudent> list = courseClassStudentPlusService.list(wrapper);
        if (CollectionUtils.isEmpty(list)){
            throw new WeChatException("未查询到学员信息");
        }
        CourseClassStudent student = list.get(0);
        if (StringUtils.isBlank(student.getOpenId())){
            CourseClassStudent classStudent = new CourseClassStudent();
            classStudent.setOpenId(ATMRContext.getContext().getOpenId());
            classStudent.setUpdateTime(DateUtil.TimestampNow());
            LambdaQueryWrapper<CourseClassStudent> modify = new LambdaQueryWrapper<>();
            modify.eq(CourseClassStudent::getStudentId,student.getStudentId());
            courseClassStudentPlusService.update(classStudent,modify);
        }
        CourseSignInMealsResp mealsResp = new CourseSignInMealsResp();
        boolean hasPayed = student.getMealsStatus().equals(ConstantUtils.Meals_Payed);
        mealsResp.setHasPayed(hasPayed);
        if (hasPayed){
            mealsResp.setMeals(BigDecimal.ZERO);
            return mealsResp;
        }
        CourseClass classByCourseNo = courseClassService.getClassByCourseNo(req.getCourseNo());
        if (Objects.isNull(classByCourseNo)){
            throw new WeChatException("课程不存在");
        }
        mealsResp.setMeals(classByCourseNo.getMeals());
        return mealsResp;
    }

    @Override
    public boolean addApplicants(CourseApplicantsReq req) {
        String openId = ATMRContext.getContext().getOpenId();
        if (StringUtils.isAnyBlank(openId, req.getCourseNo(), req.getMobile(), req.getRealName())) {
            throw new WeChatException("缺少参数");
        }

        CourseInfo courseInfo = getCourseInfoByCache(req.getCourseNo());
        if (Objects.isNull(courseInfo)){
            throw new WeChatException("课程编号参数传递有误");
        }
        LambdaQueryWrapper<CourseApplicants> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CourseApplicants::getCourseNo,req.getCourseNo());
        wrapper.eq(CourseApplicants::getMobile,req.getMobile());
        List<CourseApplicants> list = applicantsPlusService.list(wrapper);
        if (CollectionUtils.isNotEmpty(list)){
            throw new WeChatException("用户已插入不需要重复插入");
        }
        CourseApplicants applicants = new CourseApplicants();
        applicants.setRealName(req.getRealName());
        applicants.setCourseNo(req.getCourseNo());
        applicants.setMobile(req.getMobile());
        applicants.setCreateTime(DateUtil.TimestampNow());
        applicants.setPaid(ConstantUtils.Order_NoPay);
        applicants.setConsume(ConstantUtils.InValid);
        applicants.setOpenId(openId);
        return applicantsPlusService.save(applicants);
    }

    @Override
    public List<CourseApplicantsResp> applicantsList(CourseApplicantsReq req) {
        List<CourseApplicantsResp> respList = new ArrayList<>();
        LambdaQueryWrapper<CourseApplicants> applicantsWrapper = new LambdaQueryWrapper<>();
        applicantsWrapper.eq(CourseApplicants::getCourseNo,req.getCourseNo());
        List<CourseApplicants> list = applicantsPlusService.list(applicantsWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return respList;
        }
        String openId = ATMRContext.getContext().getOpenId();
        WxUser wxUser = wxUserService.findByOpenId(openId);
        list = list.stream().filter(s -> s.getOpenId().equals(openId) || s.getMobile().equals(wxUser.getMobile())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(list)) {
            return respList;
        }
        list.forEach(s -> {
            CourseApplicantsResp applicantsResp = new CourseApplicantsResp();
            applicantsResp.setMobile(s.getMobile());
            applicantsResp.setRealName(s.getRealName());
            applicantsResp.setSelf(s.getOneself().equals(ConstantUtils.Applicants));
            applicantsResp.setPaid(s.getPaid().equals(ConstantUtils.Order_Payed));
            applicantsResp.setConsume(s.getConsume().equals(ConstantUtils.Order_Payed));
            applicantsResp.setId(String.valueOf(s.getId()));
            respList.add(applicantsResp);
        });
        return respList;
    }

    @Override
    public CourseInfo getCourseInfoByCache(String courseNo) {
        try {
            Object courseObj = redisUtils.get(RedisKeyEnum.getCourseInfoKey(courseNo));
            if (Objects.nonNull(courseObj)){
                return (CourseInfo) courseObj;
            }
        } catch (Exception e) {
            log.error("缓存课程信息异常:{}",e);
        }
        CourseInfo courseInfo = courseInfoService.getInfoByNo(courseNo);
        if (Objects.nonNull(courseInfo)){
            boolean result = redisUtils.set(RedisKeyEnum.getCourseInfoKey(courseNo), courseInfo);
            log.info("缓存课程信息结果:{},课程编号：{}",result,courseNo);
            return courseInfo;
        }
        return null;
    }

    public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null;
    }

    private String replaceImageSrc(String html){
        if (StringUtils.isBlank(html)){
            return StringUtils.EMPTY;
        }
        String regex = "<img.*?src=\"(.*?)\".*?>";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(html);
        StringBuilder sb = new StringBuilder();
        int lastIndex = 0;
        while (matcher.find()) {
            // 获取src属性值
            String src = matcher.group(1);
            // 进行重新赋值
            String newSrc = "http://wx.drzh-atmr.cn"+src;
            // 拼接字符串
            //sb.append(html.substring(lastIndex, matcher.start())).append("<img src=\"").append(newSrc).append("\">");
            sb.append(html.substring(lastIndex, matcher.start())).append("<img src=\"").append(newSrc).append("\" alt=\"\" data-href=\"\" style=\"width: 100%;\">");
            lastIndex = matcher.end();
        }
        sb.append(html.substring(lastIndex));
        String newHtml = sb.toString();
        return newHtml;
    }

    private List<String> getPosterList(){
        PosterQueryCriteria criteria = new PosterQueryCriteria();
        criteria.setPosterType(1);
        criteria.setStatus(ConstantUtils.Valid);
        List<PosterDto> posterDtos = posterService.queryAll(criteria);
        if (CollectionUtil.isEmpty(posterDtos)){
            return null;
        }
        return posterDtos.stream().sorted(Comparator.comparing(PosterDto::getId, Comparator.reverseOrder())).map(PosterDto::getPosterUrl).collect(Collectors.toList());
    }

    private List<CourseClockInListResp> formatClockPageDataV4(List<CourseClass> list) {
        String openId = ATMRContext.getContext().getOpenId();
        List<CourseClockInListResp> resps = new ArrayList<>();
        for (CourseClass courseClass : list) {
            String courseNo = courseClass.getCourseNo();
            CourseClockInListResp resp = new CourseClockInListResp();
            String courseName = courseClass.getName();
            resp.setHasAccessRight(true);
            LambdaQueryWrapper<CourseClassStudent> studentWrapper = new LambdaQueryWrapper<>();
            studentWrapper.eq(CourseClassStudent::getOpenId, openId);
            studentWrapper.eq(CourseClassStudent::getCourseNo,courseClass.getCourseNo());
            List<CourseClassStudent> studentList = courseClassStudentPlusService.list(studentWrapper);
            if (CollectionUtils.isEmpty(studentList)){
                resp.setHasAccessRight(false);
                resp.setNoRightTips("非本班学员不可打卡哦");
            }
            CourseInfo courseInfo = getCourseInfoByCache(courseNo);
            resp.setCourseNo(courseClass.getCourseNo());
            resp.setCourseName(courseName);
            resp.setCourseCover(courseInfo.getCourseLogo());
            resp.setOpenTime(courseInfo.getOpenTime());
            LambdaQueryWrapper<CourseClock> wrapper1 = new LambdaQueryWrapper<>() ;
            wrapper1.eq(CourseClock::getCourseNo,courseNo);
            List<CourseClock> courseClockDtos = courseClockPlusService.list(wrapper1);
            if (CollectionUtils.isNotEmpty(courseClockDtos)) {
                resp.setHomeworkNumber(courseClockDtos.size());
                long collect = courseClockDtos.parallelStream().filter(distinctByKey(CourseClock::getOpenId)).count();
                resp.setPeopleNumber((int) collect);
                long count = courseClockDtos.parallelStream().filter(p -> p.getOpenId().equals(openId)).count();
                resp.setCompleteDays((int) count);
            }
            resps.add(resp);
        }
        return resps.stream().sorted(Comparator.comparing(CourseClockInListResp::getOpenTime, Comparator.reverseOrder())).collect(Collectors.toList());
    }
}
